# -*- coding: utf-8 -*-
# @Time    : 2024/8/29 8:45
# @Author  : Alvin
# @File    : token_auth.py
from collections import namedtuple

from authlib.jose import jwt
from authlib.jose.errors import ExpiredTokenError
from flask import current_app, request, g
from flask_httpauth import HTTPTokenAuth

from app.libs.enums import ClientTypeEnum
from app.libs.error_code import AuthFailed, Forbidden, ParameterException
from app.libs.scope import is_in_scope
from app.libs.token import generate_auth_token
from app.models.sessions import Session
from app.models.users import User

auth = HTTPTokenAuth()
User = namedtuple('User', ['uid', 'ac_type', 'scope'])


@auth.verify_token
def verify_token(token):
    print(token)
    # 这里用not 而不是判断None
    if not token:
        auth_header = request.headers.get('Authorization')
        if not auth_header:
            raise AuthFailed(msg="未携带token", error_code=1001)
        if auth_header:
            parts = auth_header.split()
            if parts[0].lower() != 'bearer':
                raise ParameterException(msg='令牌类型错误')
            if len(parts) == 2:
                token = parts[1]
            else:
                raise AuthFailed(msg="无效的token格式")

    # 默认小程序登录, 并且采用小程序验证方式
    ac_type = int(200)
    try:
        client = ClientTypeEnum(ac_type)
    except ValueError as e:
        raise ParameterException(msg="type类型错误")

    # promise = {
    #     ClientTypeEnum.USER_EMAIL: User.verify_auth_token_by_email,  # 邮箱登录
    #     ClientTypeEnum.USER_MINA: User.verify_by_code  # 微信小程序登录方式
    # }
    user_info, token = verify_auth_token(token, client)
    # print(user_info, token)
    if not user_info:
        return False
    else:
        # request
        g.user = user_info
        # g.token = token
        return True


def verify_auth_token(token, client):
    # print(client)
    # 获取密钥
    secret_key = current_app.config['SECRET_KEY']
    try:
        claims = jwt.decode(token, secret_key)
        uid = claims.get('uid', None)
        # 查看是否有sessionkey
        # 如果有则检查是否过期
        # 获取access_token
        access_token = ''
        # 判断是否过期
        # 过期则提示用户登录失效
        # 未过期则重新生成token放回给小程序端
        is_session_valid, session = Session.check_session(uid)
        if not is_session_valid:
            # 让token失效
            # 用户需要重新登录
            raise AuthFailed(msg="登录失效，请重新登录")
            pass
        claims.validate()
    except ExpiredTokenError as e:
        # 小程序端的情况
        if client == ClientTypeEnum.USER_MINA:
            # 如果token失效了，但是session仍有效，则刷新token
            # 可设置特定状态码提供前端判断是否刷新token
            if is_session_valid:
                # 重新获取token
                token = generate_auth_token(claims['uid'], claims['type'], claims['scope'])
                uid = claims['uid']
                ac_type = claims['type']
                scope = claims['scope']
                # request 视图函数
                allow = is_in_scope(scope, request.endpoint)
                if not allow:
                    raise Forbidden()
                return (User(uid, ac_type, scope), token)
            else:
                # 到这一步让用户重新登录获取session
                raise AuthFailed(msg='token过期', error_code=1003)
        else:
            raise AuthFailed(msg='token过期', error_code=1003)
    except Exception as e:
        raise AuthFailed(msg="token无效", error_code=1002)
    uid = claims['uid']
    ac_type = claims['type']
    scope = claims['scope']
    # request 视图函数
    allow = is_in_scope(scope, request.endpoint)
    if not allow:
        raise Forbidden()
    return (User(uid, ac_type, scope), None)
